package com.model.demo.spring.mini.mvcframework.v2;

import com.model.demo.spring.mini.mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class MyDispatcherServletTwo extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>();

    private Map<String,Object> ioc = new HashMap<>();

//    private Map<String, Method> handleMapping = new HashMap<>();

    private List<Handle> handleMapping =new ArrayList<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //根据请求地址匹配handleMapping,根据反射调用方法
        try {
            dispatcher(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void dispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        Handle handle = getHandle(req);
        if(handle == null){
            resp.getWriter().write("404 Not Fund!!");
        }
//        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
//        method.invoke(ioc.get(beanName), new Object[]{req, resp, parameterMap.get("name")[0]});
        Map<String, String[]> parameterMap = req.getParameterMap();
        Class<?>[] parameterTypes = handle.method.getParameterTypes();
        Object [] paramsValue = new Object[parameterTypes.length];
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            if(handle.paramsIndexMapping.containsKey(entry.getKey())){
                int index = handle.paramsIndexMapping.get(entry.getKey());
                String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "");
                paramsValue[index] = convert(parameterTypes[index], value);

            }
//
        }
         if(handle.paramsIndexMapping.containsKey(HttpServletRequest.class.getSimpleName())){
             int  index = handle.paramsIndexMapping.get(HttpServletRequest.class.getSimpleName());
             paramsValue[index] = req;
         }
        if(handle.paramsIndexMapping.containsKey(HttpServletResponse.class.getSimpleName())){
            int  index = handle.paramsIndexMapping.get(HttpServletResponse.class.getSimpleName());
            paramsValue[index] = resp;
        }

        Object returnValue = handle.method.invoke(handle.controller, paramsValue);

        if(returnValue == null || returnValue instanceof  Void){return;}
        resp.getWriter().write(returnValue.toString());

    }

    private Handle getHandle(HttpServletRequest req) {
        String uri = req.getRequestURI();
        uri  = uri.replace(req.getContextPath(),"");

        for (Handle handle : handleMapping) {
            Matcher matcher = handle.url.matcher(uri);
            if(!"".equals(uri) && matcher.matches()){
                return handle;
            }

        }
        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        try {
            //1.加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
            //2.扫描配置文件相关类
            doScanClass(properties.getProperty("scanBasePackage"));
            //3.初始化IOC容器(控制反转，beanFactory)
            doInitIOC();
            //4.依赖注入，赋值(DI)
            doAutowired();
            //5.初始化handleMapping(一个url对应一个Method)
            initHandleMapping();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initHandleMapping() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(MyController.class)|| !clazz.isAnnotationPresent(MyRequestMapping.class)){continue;}
            MyRequestMapping baseRoot = clazz.getAnnotation(MyRequestMapping.class);
            String baseRootPath = ("/" + baseRoot.value()).replaceAll("/+" ,"/");
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if(!method.isAnnotationPresent(MyRequestMapping.class)){continue;}
                String path = (baseRootPath + method.getAnnotation(MyRequestMapping.class).value()).replaceAll("/+" ,"/");
                Pattern pattern = Pattern.compile(path);
                handleMapping.add(new Handle(pattern, method, entry.getValue()));
            }


        }

    }

    private void doAutowired() throws  Exception{
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if(!field.isAnnotationPresent(MyAutowired.class)){continue;}
                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getSimpleName();
                }
                field.setAccessible(true);
                field.set(entry.getValue(), ioc.get(beanName));
            }

        }


    }

    private void doInitIOC() throws Exception{
        if(classNames.isEmpty()) {return;}
        String beanName = "";
        Object obj = null;
        for (String className : classNames) {
            Class<?> clazz = Class.forName(className);
            if(clazz.isAnnotationPresent(MyController.class)){
                obj = clazz.newInstance();
                beanName = toLowerFirstCase(clazz.getSimpleName());
            }else if(clazz.isAnnotationPresent(MyService.class)){
                obj = clazz.newInstance();
                MyService service = clazz.getAnnotation(MyService.class);
                beanName = service.value();
                if("".equals(beanName)){
                    beanName = toLowerFirstCase(clazz.getSimpleName());
                }
            }else{
                continue;
            }
            ioc.put(beanName, obj);
            if(clazz.getInterfaces() != null && clazz.getInterfaces().length > 0){
                for (Class<?> clazzInterface : clazz.getInterfaces()) {
                    if(ioc.containsKey(clazzInterface.getSimpleName())){
                        throw new Exception("the bean is exist!!");
                    }
                    ioc.put(clazzInterface.getSimpleName(), obj);
                }
            }



        }

    }

    private void doScanClass(String scanBasePackage) {
        URL resource = this.getClass().getClassLoader().getResource("/" + scanBasePackage.replaceAll("\\.", "/"));
        String resourceFileName = resource.getFile();
        File resourceFile = new File(resourceFileName);
        for (File file : resourceFile.listFiles()) {
            if(file.isDirectory()){
                doScanClass(scanBasePackage + "." + file.getName());
            }else{
                if(!file.getName().endsWith(".class")){continue;}
                String className = scanBasePackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }

        }

    }

    private void doLoadConfig(String contextConfigLocation) throws Exception {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        properties.load(resourceAsStream);
    }

    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        chars[0] += 32;
        return  String.valueOf(chars);
    }

    //url传过来的参数都是String类型的，HTTP是基于字符串协议
    //只需要把String转换为任意类型就好
    private Object convert(Class<?> type,String value){
        if(Integer.class == type){
            return Integer.valueOf(value);
        }
        //如果还有double或者其他类型，继续加if
        //这时候，我们应该想到策略模式了
        //在这里暂时不实现，希望小伙伴自己来实现
        return value;
    }
    
    private class Handle{
        private Pattern url;
        private Method method;
        private Object controller;
        
        private Map<String,Integer>paramsIndexMapping ;

        public Handle(Pattern url, Method method, Object controller) {
            this.url = url;
            this.method = method;
            this.controller = controller;
            paramsIndexMapping = new HashMap<>();
            putParamIndexMap(method);
        }

        private void putParamIndexMap(Method method) {
            //处理MyRequestMapping注解形参
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (Annotation annotation : parameterAnnotations[i]) {
                    if(annotation instanceof MyRequestParam){
                        MyRequestParam myRequestMapping = (MyRequestParam) annotation;
                        String value = myRequestMapping.value();
                        paramsIndexMapping.put(value, i);
                    }
                }
            }
            //处理特殊类型形参    
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if(parameterTypes[i] == HttpServletRequest.class){
                    paramsIndexMapping.put(parameterTypes[i].getSimpleName(), i);
                }else if(parameterTypes[i] == HttpServletResponse.class){
                    paramsIndexMapping.put(parameterTypes[i].getSimpleName(), i);
                }

            }

        }
    }
}
